/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include <map>
#include <chrono>
#include <thread>
#include <fstream>
#include <iostream>
#include <functional>
#include "BackintConst.h"
#include "common/File.h"
#include "RestoreHandler.h"
#include "common/MpString.h"
#include "common/JsonHelper.h"

using namespace std::chrono;
using namespace backint;
using namespace Module;

namespace {
const int MAX_BACKUP_THREDS = 50;
const int DEFAULT_RESTORE_THREDS = 2;
const std::string RESTORE_THREAD_KEY = "RestoreHandler";

const std::string RESTORE_SUCCESS = "#RESTORED";
const std::string RESTORE_ERROR = "#ERROR";
const std::string VERSION_1_0 = "1.0";
const int READ_WRITE_DATA_BUFFER = 4096;
}

class XTaskRestore : public ExecutableItem {
public:
    // 三个路径：源路径，目标路径，标准输出路径
    XTaskRestore(const std::string &src, const std::string &dst, const std::string &prt,
    const std::string &backupId, int64_t size)
        : m_strFile(src), m_dstFile(dst), m_prtFile(prt), m_backupId(backupId), m_dataSize(size)
    {
        m_result = 0;
    }

    virtual ~XTaskRestore()
    {}
    virtual void Exec();

    std::string GetDstFile()
    {
        return m_dstFile;
    }
    std::string GetSrcFile()
    {
        return m_strFile;
    }
    void SetLogLocalBackup()
    {
        m_logLocalBackup = true;
    }
private:
    std::string m_strFile;
    std::string m_dstFile;
    std::string m_prtFile;
    std::string m_backupId;
    int64_t m_dataSize = 0;
    bool m_logLocalBackup = false;
};

void XTaskRestore::Exec()
{
    std::ifstream inFile(m_strFile.c_str(), std::ios::in | std::ios::binary);
    if (!inFile.is_open()) {
        ERRLOG("Open backup file failed, restore file:%s, errno:%d", m_strFile.c_str(), GetLastError());
        m_result = -1;
        std::cout << RESTORE_ERROR << " " << m_dstFile << std::endl;
        return;
    }

    std::ofstream outFile(m_dstFile.c_str(), std::ios::out | std::ios::binary);
    if (!outFile.is_open()) {
        ERRLOG("Open dest file failed, restore file:%s, errno:%d", m_dstFile.c_str(), GetLastError());
        inFile.close();
        m_result = -1;
        std::cout << RESTORE_ERROR << " " << m_dstFile << std::endl;
        return;
    }

    std::streamsize totalSize = 0;
    char readBuffer[READ_WRITE_DATA_BUFFER + 1] = {0};
    while (!inFile.eof()) {
        inFile.read(readBuffer, READ_WRITE_DATA_BUFFER);
        std::streamsize bytesRead = inFile.gcount();
        totalSize += bytesRead;
        outFile.write(readBuffer, bytesRead);
    }
    inFile.close();
    outFile.close();
    m_result = 0;
    if (m_logLocalBackup) {
        printf("%s %s %s %zu\n", RESTORE_SUCCESS.c_str(), m_backupId.c_str(), m_prtFile.c_str(), totalSize);
        DBGLOG("Exit m_backupID: %s, restore file:%s, size: %lu", m_backupId, m_strFile.c_str(), totalSize);
    } else {
        printf("%s %s %s %zu\n", RESTORE_SUCCESS.c_str(), m_backupId.c_str(), m_dstFile.c_str(), totalSize);
        DBGLOG("Exit m_backupID: %s, restore file:%s, size: %lu", m_backupId, m_dstFile.c_str(), totalSize);
    }
}

RestoreHandler::RestoreHandler()
{
    ThreadPoolFactory::DestoryThreadPool(RESTORE_THREAD_KEY);
}

void RestoreHandler::CheckTaskResult(size_t &nJobs, bool &result)
{
    const int waitMs = 5;
    std::shared_ptr<ExecutableItem> treadPoolRes;
    while (m_jsPtr->Get(treadPoolRes, true, waitMs)) {
        nJobs--;
        XTaskRestore *xTask = (XTaskRestore *)treadPoolRes.get();
        if (nullptr == xTask) {
            result = false;
            ERRLOG("xTask is null");
            break;
        }

        if (xTask->Result() != 0) {
            result = false;
            ERRLOG("Task is failed, restore file:%s", xTask->GetDstFile().c_str());
            break;
        }
        ++m_restoredCount;
        DBGLOG("Task is success[%d/%d], restore file:%s", m_restoredCount, m_resotreTotal, xTask->GetDstFile().c_str());
    }
}

void RestoreHandler::RestoreTaskInner(size_t &nJobs, bool &result)
{
    std::shared_ptr<ExecutableItem> treadPoolRes;
    while (nJobs) {
        if (!m_jsPtr->Get(treadPoolRes, true)) {
            ERRLOG("Get failed.");
            result = false;
            break;
        }

        --nJobs;
        XTaskRestore *xTask = (XTaskRestore *)treadPoolRes.get();
        if (nullptr == xTask) {
            ERRLOG("xTask is null");
            result = false;
            break;
        }

        if (xTask->Result() != 0) {
            result = false;
            ERRLOG("Task is failed, restore file:%s", xTask->GetDstFile().c_str());
        }
        ++m_restoredCount;
        DBGLOG("Task is success[%d/%d], restore file:%s", m_restoredCount, m_resotreTotal, xTask->GetDstFile().c_str());
    }
}

bool RestoreHandler::ExecutTask(size_t nJobs, const StuFileData &fileData)
{
    std::string fileName;
    std::string backupId;

    std::string srcFilePath;
    std::string dstFilePath;
    std::string prtFilePath;
    std::vector<std::string> vesConents;
    Module::CMpString::StrSplit(vesConents, fileData.m_filePath, ' ');
    const size_t dataRestoreVesCont = 2;
    const size_t logRestoreVesCont = 3;
    const int dstFileIdx = 2;

    size_t contentSize = vesConents.size();
    if (contentSize == dataRestoreVesCont) {
        // format: BID destFilePath
        backupId = vesConents[0];
        dstFilePath = vesConents[1];
        prtFilePath = vesConents[1];
        fileName = GetFileName(dstFilePath);
        srcFilePath = m_dataPath + PATH_SEP_CHAR + fileName;
    } else if (contentSize == logRestoreVesCont) {
        backupId = vesConents[0];
        prtFilePath = vesConents[1];
        fileName = GetFileName(vesConents[1]);
        srcFilePath = m_dataPath + PATH_SEP_CHAR + fileName;
        dstFilePath = vesConents[dstFileIdx] + PATH_SEP_CHAR + GetFileName(srcFilePath);
    } else {
        ERRLOG("invalid content");
        return false;
    }
    std::shared_ptr<XTaskRestore> task(
        new (std::nothrow) XTaskRestore(srcFilePath, dstFilePath, prtFilePath, backupId, fileData.m_dataLength));
    if (task == nullptr) {
        ERRLOG("tp is nullptr");
        return false;
    }
    if (contentSize == logRestoreVesCont) {
        task->SetLogLocalBackup();
    }
    DBGLOG("Put task restore file:%s", fileData.m_filePath.c_str());
    if (!m_jsPtr->Put(task)) {
        ERRLOG("Put failed, restore file:%s", fileData.m_filePath.c_str());
        return false;
    }
    return true;
}

uint32_t RestoreHandler::DataRestoreOra(const StuParam &inParam, StuFileInfo &stFileInfo)
{
    LOGGUARD("");
    m_jsPtr = std::make_shared<JobScheduler>(*ThreadPoolFactory::GetThreadPoolInstance(RESTORE_THREAD_KEY, m_channels));
    if (m_jsPtr == nullptr) {
        ERRLOG("tp malloc failed");
        return FAILED;
    }

    size_t nJobs = 0;
    bool result = true;
    size_t index = 0;

    m_resotreTotal = (uint32_t)(stFileInfo.fileData.size());
    INFOLOG("The channels:%d, total restore file size: %d", m_channels, m_resotreTotal);
    for (; index < m_resotreTotal;) {
        if (nJobs < m_channels) {
            if (!ExecutTask(nJobs, stFileInfo.fileData[index])) {
                result = false;
                break;
            }
            ++index;
            ++nJobs;
        }
        CheckTaskResult(nJobs, result);
        if (!result) {
            break;
        }
    }
    DBGLOG("nJobs: %d", nJobs);
    RestoreTaskInner(nJobs, result);
    INFOLOG("Exit DataRestoreOra, nJobs: %d", nJobs);
    return result ? SUCCESS : FAILED;
}

uint32_t RestoreHandler::ExecRestore(const StuParam &inParam, const PlugParams &plugParam)
{
    GetRestoreParam(inParam, plugParam);
    StuFileInfo stFileInfo;
    if (backint::GetBackupList(inParam.m_inputFile, stFileInfo) != SUCCESS) {
        ERRLOG("Failed to read pipe info.");
        return FAILED;
    }
    if (stFileInfo.fileData.empty()) {
        ERRLOG("file info is empty.");
        return FAILED;
    }
    return DataRestoreOra(inParam, stFileInfo);
}

void RestoreHandler::GetRestoreParam(const StuParam &inParam, const PlugParams &plugParam)
{
    m_pid = plugParam.m_pid;
    m_jobId = plugParam.m_jobId;
    m_subJobId = plugParam.m_subJobId;
    m_cachePath = plugParam.m_cachePath;
    m_dataPath = plugParam.m_dataPath;
    m_outputFile = inParam.m_outFile;
    m_backupID = inParam.m_dtBackupId;

    uint32_t cpuNum = GetCpuNum();
    if (plugParam.m_channels > 0 && (uint32_t)plugParam.m_channels <= cpuNum) {
        m_channels = plugParam.m_channels;
    }
}